package model.PokerTP;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;

import play.libs.Akka;
import scala.concurrent.duration.Duration;
import model.messages.*;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActor;

public class ActorTable extends UntypedActor {
	private Map<String, ActorRef> gracze = new HashMap<String, ActorRef>();
	private List<ActorRef> kolejkaRozgrywki = new ArrayList<ActorRef>();
	private int humanPlayers = 0;
	private int neededHumanPlayers = 0;
	private int computerPlayers = 0;
	private int neededComputerPlayers = 0;
	private int entranceFee = 0;
	private int startTokens = 1;
	//private GamePhase faza = null;
	private ActorRef currentPlayer = null;
	//private Deck deck = null;
	private CurrentPlay currentPlay;
	private boolean gameInProgress = false;
	private boolean inited = false;
	private long tableId;
	
	public ActorTable(long id, int humanPlayers, int computerPlayers, int entranceFee, int startTokens) {
		this.neededHumanPlayers = this.humanPlayers = humanPlayers;
		this.neededComputerPlayers = this.computerPlayers = computerPlayers;
		this.entranceFee = entranceFee;
		this.startTokens = startTokens;
		inited = true;
	}
	
	@Override
	public void onReceive(Object message) throws Exception {
		// TODO Automatycznie generowany szkielet metody
		if(message instanceof TableSettings) {
			initTable((TableSettings)message);
		}
		else if(message instanceof PlayerJoin) {
			joinPlayer((PlayerJoin)message, getSender());
		}
		else if(message instanceof StartSignal) {
			startGame(getSender());
		}
		else if(message instanceof PlayerLeave) {
			playerLeave((PlayerLeave)message);
		}
		else if(message instanceof PlayerBiddingAction) {
			doPhase(getSender(), (PlayerBiddingAction)message);
		}
		else if(message instanceof PlayerCardChange) {
			doPhase(getSender(), (PlayerCardChange)message);
		}
		else
			unhandled(message);
	}
	
	public void notifyAll(Object message) {
		ActorRef self = getSelf();
		for(ActorRef player : kolejkaRozgrywki) {
			player.tell(message, self);
		}
	}
	
	public static enum GamePhase {
		ENTRANCE_FEE, DEAL_CARDS, FIRST_BIDDING, EXCHANGE, SECOND_BIDDING, FIGHT, SHOW_WINNER;
		
		public GamePhase getNext() {
			return (this.ordinal() < GamePhase.values().length - 1) ? GamePhase.values()[this.ordinal() + 1]
																	: null;
			
		}
	}
	
	private void initTable(TableSettings settings) {
		if(settings == null) throw new NullPointerException();
		neededHumanPlayers = humanPlayers = settings.humanPlayers;
		neededComputerPlayers = computerPlayers = settings.computerPlayers;
		entranceFee = settings.entranceFee;
		startTokens = settings.startTokens;
		inited = true;
	}
	
	private void joinPlayer(PlayerJoin p, ActorRef sender) {
		int leftspace = p.isComputer ? neededComputerPlayers : neededHumanPlayers;
		if(leftspace <= 0) {
			sender.tell(new model.messages.Error("Przy stole nie ma juz wolnych miejsc."), getSelf());
			return;
		} else {
			if(gracze.containsKey(p.name) && !p.isComputer) {
				sender.tell(new model.messages.Error("Dany gracz jest juz przy stole."), getSelf());
				return;
			}
			gracze.put(p.name, p.actor);
			kolejkaRozgrywki.add(p.actor);
			notifyAll(new TableMessage("Dołączył do gry.", p.name));
			if(p.isComputer) --neededComputerPlayers;
			else --neededHumanPlayers;
		}
		
		if(neededComputerPlayers == 0 && neededHumanPlayers == 0)
			Akka.system().scheduler().scheduleOnce(
					Duration.create(10, TimeUnit.SECONDS),
					new Runnable() {
						@Override
						public void run() {
							getSelf().tell(new StartSignal(), getSelf());
						}
					},
					Akka.system().dispatcher()
					);
	}
	
	private void startGame(ActorRef sender) {
		if(neededComputerPlayers + neededHumanPlayers != 0) return;
		
		currentPlay = new CurrentPlay();
		
		// Losowa kolejność
		Random r = new Random();
		int indeks = r.nextInt(kolejkaRozgrywki.size());
		List<ActorRef> tmpList = new ArrayList<ActorRef>(kolejkaRozgrywki.size());
		for(int i = indeks; i < kolejkaRozgrywki.size(); ++i) {
			tmpList.add(kolejkaRozgrywki.get(i));
		}
		for(int i = 0; i < indeks; ++i) {
			tmpList.add(kolejkaRozgrywki.get(i));
		}
		kolejkaRozgrywki = tmpList;
		
		gameInProgress = true;
		currentPlay.isStarted = true;
		currentPlay.deck = new Deck();
		nextPhase(); // rozpoczęcie od ENTRANCE_FEE
		
		doPhase(sender);
	}
	
	private void nextPhase() {
		if(currentPlay.gamePhase == null)
			currentPlay.gamePhase = GamePhase.ENTRANCE_FEE;
		else
			currentPlay.gamePhase = currentPlay.gamePhase.getNext();
		// jeśli getNext zwróciło null, znaczy że wszystkie fazy zostały wykonane - koniec rozgrywki
		if(currentPlay.gamePhase == null) {
			currentPlay = null;
			gameInProgress = false;
			return;
		}
		currentPlay.currentPlayerIndex = 0;
		currentPlay.currentPlayer = kolejkaRozgrywki.get(0);
		currentPlay.movesLeftInCurrentPhase = kolejkaRozgrywki.size();
		
		if(currentPlay.gamePhase != GamePhase.EXCHANGE) {
			doPhase(getSelf());
		}
	}
	
	private void doPhase(ActorRef sender, Object... params) {
		if(currentPlay == null || currentPlay.gamePhase == null) { 
			getSelf().tell(new StartSignal(), getSelf());
			return;
		}
		
		switch(currentPlay.gamePhase) {
		case ENTRANCE_FEE:
			phaseEntranceFee(sender);
			currentPlay.movesLeftInCurrentPhase = 0;
			break;
		case DEAL_CARDS:
			phaseDealCards(sender);
			currentPlay.movesLeftInCurrentPhase = 0;
			break;
		case FIRST_BIDDING:
			phaseFirstBidding(sender, params);
			currentPlay.movesLeftInCurrentPhase = 0;
			break;
		case EXCHANGE:
			phaseExchange(sender, params);
			currentPlay.movesLeftInCurrentPhase -= 1;
			break;
		case SECOND_BIDDING:
			phaseSecondBidding(sender, params);
			currentPlay.movesLeftInCurrentPhase = 0;
			break;
		case FIGHT:
			phaseFight(sender);
			currentPlay.movesLeftInCurrentPhase = 0;
			break;
		case SHOW_WINNER:
			phaseShowWinner(sender);
			currentPlay.movesLeftInCurrentPhase = 0;
			break;
		}
		
		if(currentPlay.movesLeftInCurrentPhase == 0 /*&& (currentPlay.gamePhase == GamePhase.FIRST_BIDDING || currentPlay.gamePhase == GamePhase.SECOND_BIDDING
				|| currentPlay.gamePhase == GamePhase.EXCHANGE)*/) {
			// Wyzerowanie licznika i przejscie do nastepnej fazy
			nextPhase();
		}
	}
	
	private void phaseEntranceFee(ActorRef sender) {
		final int entrancefee = entranceFee;
		notifyAll(new TableAskForAction(
				String.format("Wymagana jest wpłata wpisowego: %d. Odmówienie wpłaty spowoduje wyrzucenie ze stołu.", entrancefee),
				new TableAskForAction.Action() {
					@Override
					public boolean performAction(Object[] args) {
						if(args == null || args.length != 1 || !(args[0] instanceof Player))
							return false;
						else {
							int ent = entrancefee;
							Player pl = (Player)args[0];
							if(pl.cash < entrancefee)
								return false;
							else{								
								pl.decreaseCash(ent);
								return true;
							}
						}
						
					}
				}));
	}
	
	private void phaseDealCards(ActorRef sender) {
		if(currentPlay.deck == null) currentPlay.deck = new Deck();
		for(ActorRef actor : kolejkaRozgrywki) {
			actor.tell(new PlayerCardSet(currentPlay.deck), getSelf());
		}
	}
	
	private void phaseFirstBidding(ActorRef sender, Object[] params) {
		if(params == null || !(params[0] instanceof PlayerBiddingAction) || currentPlay.gamePhase != GamePhase.FIRST_BIDDING)
			if(sender != null) sender.tell(new TablePopupMessage("Proszę czekać na swoją kolej..."), getSelf());
		if(currentPlay.licytacja == null) currentPlay.actorLicytacja = Akka.system().actorOf(Props.create(ActorLicytacja.class));
		PlayerBiddingAction pba = (PlayerBiddingAction)params[0];
		currentPlay.actorLicytacja.tell(pba, getSender());
	}
	
	private void phaseExchange(ActorRef sender, Object[] params) {
		if(params == null || !(params[0] instanceof PlayerCardChange))
			if(sender != null) sender.tell(new TablePopupMessage("Proszę czekać na swoją kolej..."), getSelf());
		if(currentPlay.currentPlayer.compareTo(sender) == 0) { 
			sender.tell(new PlayerNewCards(currentPlay.deck, ((PlayerCardChange)params[0]).numOfCards), getSelf());
		}
		else
			if(sender != null) sender.tell(new TablePopupMessage("Proszę czekać na swoją kolej..."), getSelf());
	}
	
	private void phaseSecondBidding(ActorRef sender, Object[] params) {
		if(params == null || !(params[0] instanceof PlayerBiddingAction) || currentPlay.gamePhase != GamePhase.SECOND_BIDDING)
			if(sender != null) sender.tell(new TablePopupMessage("Proszę czekać na swoją kolej..."), getSelf());
		// Może wyrzucić NullPointerException
		PlayerBiddingAction pba = (PlayerBiddingAction)params[0];
		currentPlay.actorLicytacja.tell(pba, getSender());
	}
	
	private void phaseFight(ActorRef sender) {
		
	}
	
	private void phaseShowWinner(ActorRef sender) {
		// TODO Usunąć obecną talię
	}
	
	private void playerLeave(PlayerLeave pl) {
		if(!gameInProgress) {
			ActorRef actor = gracze.get(pl.name);
			if(actor == null) return;
			kolejkaRozgrywki.remove(actor);
			gracze.remove(pl.name);
			notifyAll(new TableMessage("Opuścił stół.", pl.name));
		}
	}
	
	
	private class CurrentPlay {
		public int betHigh;
		public GamePhase gamePhase = null;
		public int movesLeftInCurrentPhase;
		public int currentPlayerIndex;
		public ActorRef currentPlayer;
		public boolean isStarted = false;
		public Deck deck;
		public Licytacja licytacja;
		public ActorRef actorLicytacja;
	}
	
}
